---
title: Architecture
summary: Layering, routing, the dependency injection composition root, options binding, and error handling.
status: draft
owner: Architecture
last_reviewed: 2026-10-09
---

# Architecture

DBConnectionAPI is a small ASP.NET Core REST API with a thin layering: two controllers delegate to a single singleton service, `ConnectionManager`, which composes database connection strings from templates and secure settings held in the SAIF Environment API, validates Oracle and SQL Server strings with a throw-away NHibernate connectivity probe, and caches the validated strings in process memory. The service has no entities, repositories, DTO classes or minimal-API endpoints. Host model and middleware order are documented on the hosting pipeline page.

| Aspect | Finding |
| --- | --- |
| Target framework | `net10.0` |
| Host model | Generic host with `UseStartup<Startup>()` — see [Hosting pipeline](hosting-pipeline.md) |
| Startup type | `Startup` : `CoreAPIStartup` (`DBConnectionAPI/DBConnectionAPI/Startup.cs`) |

## Layer architecture

```mermaid
flowchart TD
    CLIENT["Client request"] --> PIPE["Middleware pipeline<br/>see hosting-pipeline.md"]
    PIPE --> AUTH["Authorization attribute<br/>SecurityApiAuthorize on both controllers"]
    AUTH --> CTRL["ConnectionsController and AdminController<br/>ControllerBase"]
    CTRL --> SVC["Service layer<br/>IConnectionManager / ConnectionManager singleton"]
    SVC --> ENV["Environment client<br/>IEnvironmentClient templates and secure settings"]
    SVC --> NH["Data access<br/>IUnitOfWorkFactory connectivity probes"]
    NH --> DB["Caller-named Oracle or SQL Server database"]
    CTRL --> ERR["Error handling<br/>ConnectionManagerException handled by package middleware"]
```

## Layer responsibilities

| Layer | Type | Responsibility |
| --- | --- | --- |
| `Program.cs` | Host entry point | Builds and runs the generic host, adds `ConfigureOpenTelemetry`, and invokes `Startup` through `UseStartup<Startup>()` |
| `Startup` | `CoreAPIStartup` subclass | Service registration (`ConfigureServices`) and pipeline composition (`Configure`) |
| `ConnectionsController` | `ControllerBase` | Three GET endpoints that return composed connection strings; writes an audit log entry per request using `IIdentityServiceAccessor` |
| `AdminController` | `ControllerBase` | Three POST endpoints that return the raw credential parts and composed connection string, without probe or cache |
| `ConnectionManager` | Singleton service (`IConnectionManager`) | Cache, key generation, template formatting, credential retrieval, probe with retry, and error mapping |
| `ConnectionManagerException` | `HttpStatusCodeException` subclass | Carries the HTTP status code (400, 404 or 500) and `application/json` content type |
| `EnvironmentKeys` | Static class | Environment API group name and template key names |
| `IUnitOfWorkFactory` | `SAIF.NetCore.NHibernate` package type† | Opens a unit of work for each probe; the API defines no entities, mappings or repositories |

## Routing configuration

| Aspect | Finding |
| --- | --- |
| Controller registration | Not visible in source; performed inside the opaque `base.ConfigureServices` call† |
| Endpoint mapping | Inside opaque `base.Configure` (not visible in source); the package documentation lists `UseEndpoints(MapControllers)`† |
| Route template style | Literal `v1` path segment: `v1/Connections` and `v1/Admin` plus the action name, each a class-level `[Route]` template with a controller-name token |
| Versioning | None. No versioning package or `[ApiVersion]` attribute exists; `v1` is a fixed path segment |
| Controller folder layout | `DBConnectionAPI/DBConnectionAPI/Controllers/` (two controllers) |

## Base controller

Both controllers derive from `ControllerBase` and carry `[ApiController]`. Neither derives from a local or SAIF base class, so no inherited behavior is added beyond the framework defaults. `[ApiController]` enables automatic 400 responses for invalid model state; whether the opaque base suppresses that behavior is not visible in source.**

| Controller | Base | `[ApiController]` | Inherited behavior |
| --- | --- | --- | --- |
| `ConnectionsController` | `ControllerBase` | Yes | — |
| `AdminController` | `ControllerBase` | Yes | — |

## Composition root

| Field | Value |
| --- | --- |
| **Project** | `DBConnectionAPI` |
| **File** | `DBConnectionAPI/DBConnectionAPI/Startup.cs` |
| **Entry method** | `Startup.ConfigureServices(IServiceCollection)` |
| **Invoked from** | `UseStartup<Startup>()` in `DBConnectionAPI/DBConnectionAPI/Program.cs` |
| **Base-class registrations** | `base.ConfigureServices(services)` — `CoreAPIStartup` (`SAIF.NetCore.ServiceExtensions` 10.2026.26267.1), not visible in source |
| **Additional registration files** | None |

The registration evidence is `ConfigureServices(IServiceCollection)`, `AddSingleton<IConnectionManager, ConnectionManager>()`, `AddNhibernateServices<Startup>()` and `AddSwaggerGen(options)`, all in `Startup.cs`. No `IServiceCollection` extension class, `builder.Services`, `AddHttpClient`, `AddDbContext`, `ConfigureOptions<T>`, `AddHostedService<T>` or `AddPingable*` registration exists elsewhere in the project. `Program.cs` reaches `Startup` through `UseStartup<Startup>()`, which confirms it is the root the running service uses.

The `Startup` constructor calls `EnableSecureEnvironmentClient<Startup>()`. `ConfigureServices` calls `base.ConfigureServices(services)` first, so opaque package registrations precede the local ones. `EnableNewtonsoftJsonSerializer` and `DisableAuthServices` are not set.

| Service type | Implementation | Lifetime | Registered in | Resolved by | Purpose |
| --- | --- | --- | --- | --- | --- |
| `IConnectionManager` | `ConnectionManager` | Singleton (holds a process-wide connection cache guarded by a lock) | `DBConnectionAPI/DBConnectionAPI/Startup.cs` | `ConnectionsController`, `AdminController` | Connection-string composition, validation and caching |
| `IUnitOfWorkFactory` and `UnitOfWorkAccessor` | Registered by `AddNhibernateServices<Startup>()` | Singleton and scoped respectively† | `DBConnectionAPI/DBConnectionAPI/Startup.cs` | `ConnectionManager` | Opens units of work for connectivity probes |
| Swagger generator services | `AddSwaggerGen(options)` | Framework-managed | `DBConnectionAPI/DBConnectionAPI/Startup.cs` | Swagger middleware | OpenAPI document `v1` |
| `IEnvironmentClient`, `ISystemHashService`, `ISaifLogService`, `IIdentityServiceAccessor`, `IDBConnectionClient` | — | — | registered by `SAIF.NetCore.ServiceExtensions` (not visible in source) | `ConnectionManager`, `AdminController`, `ConnectionsController` | Environment settings, secure-setting hashing, logging and identity; `IDBConnectionClient` is registered by the base† but this API does not resolve it |

## Options binding

The service binds no options through the options pattern. No `Configure<T>`, `AddOptions<T>`, `IConfigureOptions<T>` or `IConfiguration` usage exists in source. Settings reach `ConnectionManager` through `IEnvironmentClient` calls, documented on the configuration page.

| Options type | Binding | Configuration source | Consumer |
| --- | --- | --- | --- |
| `UnitOfWorkOptions` (`SAIF.NetCore.NHibernate`) | Inline object initializer per probe, not registered | Composed connection string | `ConnectionManager.TestOracleConnection`, `ConnectionManager.TestSqlConnection` |
| `SwaggerGenOptions` | `AddSwaggerGen` lambda | Constants and `AppContext.BaseDirectory` | Swagger generator |

## Error handling

| Mechanism | Registered in | Scope | Behavior |
| --- | --- | --- | --- |
| Typed `ConnectionManagerException : HttpStatusCodeException` handled by package middleware `DynamicErrorHandler`† | Thrown in `DBConnectionAPI/DBConnectionAPI/Services/ConnectionManager.cs`; handler registered inside the opaque base | Three Connections endpoints | Missing secure setting yields 404; Oracle TNS alias errors yield 400; every other probe failure yields 500. The exception message is returned; the exact envelope is written by the package† |
| Local `try`/`catch` in `AdminController.GetConnectionInfo` | `DBConnectionAPI/DBConnectionAPI/Controllers/AdminController.cs` | Per-action (three Admin endpoints) | A failed credential split is swallowed; the action returns 200 with an `Error` entry in the dictionary |
| `[ApiController]` automatic model-state 400 | Framework | All actions | `ValidationProblemDetails`; no validation rule exists that would trigger it for string parameters* |
| Retry-internal `try`/`catch` | `DBConnectionAPI/DBConnectionAPI/Services/ConnectionManager.cs` | Probe loop | Logs each failure through `ISaifLogService`, retries or fails fast, then rethrows a mapped `ConnectionManagerException` |

No `UseExceptionHandler`, `AddProblemDetails`, `IExceptionHandler`, exception filter or global filter exists in source. When an exception other than `HttpStatusCodeException` escapes (for example a credential value without a pipe separator), the resulting response is produced by opaque package middleware and its status is not visible in source.** Messages on 400 and 500 responses expose the cache key parts, the secure-setting key name, the Environment API track name and, for some failures, the raw provider message, so exception detail crosses the service boundary.

## Serialization

| Aspect | Finding |
| --- | --- |
| Serializer | Decided inside the opaque base; `EnableNewtonsoftJsonSerializer` is not set, so System.Text.Json is the framework default* |
| Configured in | No `AddJsonOptions` or `AddNewtonsoftJson` call in source |
| Converters and settings | Framework defaults; Connections actions return `string`, Admin actions return `Dictionary<string,string>` |

## Design constraints
- The connection cache is a process-lifetime dictionary inside a singleton and is never evicted, so credential rotation takes effect only after the process restarts.
- Responses carry credential material by design: connection strings from the Connections endpoints, and raw credentials plus plaintext password from the Admin endpoints.
- The API resolves no connection for its own use; it composes strings for callers and probes only Oracle and SQL Server. PostgreSQL strings are not probed.
- Authentication, authorization middleware, exception handling and endpoint mapping run inside the opaque `base.Configure` call, so their order is not visible in source.

## Integration points

| System | Type | Purpose |
| --- | --- | --- |
| SAIF Environment API | Downstream API (through `IEnvironmentClient`) | Connection templates and secure credentials in group `dbconnectionapi`† |
| Security API | Downstream API (through `SecurityApiAuthorize`) | Resource authorization for `DBConnAPIRetrieveResource`† |
| Oracle and SQL Server databases named by the caller | Database | Read-only connectivity probes |

> **Note:** Statements marked with `†` are derived from dependency library
> documentation via the Legends CLI. Statements marked with `*` were inferred
> from code structure or contextual evidence. Statements marked with `**` could
> not be confirmed; treat as unknown until verified.

## Related documentation
- [API index](index.md)
- [Overview](overview.md)
- [Hosting pipeline](hosting-pipeline.md)
- [Endpoints](endpoints.md)
- [Authentication and authorization](authentication-authorization.md)
- [Dependencies](dependencies.md)
- [Configuration](configuration.md)
- [Operations and processes](operations-processes.md)
- [Business rules](business-rules.md)
- [Data model](data-model.md)
- [Operational runbook](operational-runbook.md)
- [Tech debt](tech-debt.md)

## Sources
- DBConnectionAPI/DBConnectionAPI/Program.cs
- DBConnectionAPI/DBConnectionAPI/Startup.cs
- DBConnectionAPI/DBConnectionAPI/DBConnectionAPI.csproj
- DBConnectionAPI/DBConnectionAPI/Controllers/ConnectionsController.cs
- DBConnectionAPI/DBConnectionAPI/Controllers/AdminController.cs
- DBConnectionAPI/DBConnectionAPI/Services/IConnectionManager.cs
- DBConnectionAPI/DBConnectionAPI/Services/ConnectionManager.cs
- DBConnectionAPI/DBConnectionAPI/Exceptions/ConnectionManagerException.cs
- DBConnectionAPI/DBConnectionAPI/EnvironmentKeys.cs
